package com.yj.io.nio;


import javax.swing.filechooser.FileSystemView;
import java.io.*;
import java.net.*;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by yinJ by 2022/5/10
 * 分片下载
 */
public class FileSpiltAndMerge {

    public static void main(String[] args) throws IOException, InterruptedException, URISyntaxException {
        //是否删除分片的目录
        boolean delete = true;
        //获取桌面页面
        File desktopDir = FileSystemView.getFileSystemView().getHomeDirectory();

//        String pathurl = "http://127.0.0.1/upload/aaa.mp4";
//        String pathurl = "http://127.0.0.1/upload/dqm2.cci";
        String pathurl = "https://v3-default.ixigua.com/d1d5a61f4873557d59cef9cfa4fd091f/628c90b4/video/tos/cn/tos-cn-v-6f4170/1ac798d04da74eb3b1062b0a5ef5420d/";
//        String pathurl = "https://img-baofun.zhhainiao.com/pcwallpaper_ugc/preview/77e2d7b1262c171a1f49a7cb7aa18bb5_preview.mp4";

        //获取后缀名
        String suffix;
        if (pathurl.contains("?")) {
            String substring = pathurl.substring(0, pathurl.lastIndexOf('?'));
            suffix = substring.substring(substring.lastIndexOf('.') + 1, substring.length());
        } else {
            suffix = pathurl.substring(pathurl.lastIndexOf('.') + 1, pathurl.length());
        }
        //指定文件的名字
        String name = "";
        //如果url没有后缀，请自己指定
        suffix = "mp4";
        System.out.println("后缀名:" + suffix);
        URL url = new URL(pathurl);
//        URL url = new URL("http://192.168.83.40/upload/aaa.jpg");

        //URLConnection	openConnection()
        //返回一个URLConnection实例表示由所引用的远程对象的连接URL 。
//        URLConnection urlConnection = url.openConnection();
        //abstract void	connect()
        //如果尚未建立此类连接，则打开此URL引用的资源的通信链接。
//        urlConnection.connect();
        //InputStream	getInputStream()
        //返回从此打开的连接读取的输入流。

        //从连接获取文件
        long start = System.currentTimeMillis();
        String data = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        String path = desktopDir.getAbsolutePath() + "/yj" + data;

        //多线程网络文件下载
//        spilt2(url, path); //只能下载几百m文件
//        spilt3(url, path);
//        merge(path, desktopDir.getAbsolutePath() + "/" + name + data + "." + suffix, delete);

        //单线程网络文件下载
        spilt4(url, desktopDir.getAbsolutePath() + "/" + name + data + "." + suffix);


        //本地文件传输
//        spilt("D:\\software\\idea_project\\test\\netty\\io\\src\\main\\java\\com\\yj/io/nio/img/" + name + "." + suffix,
//                "D:\\software\\idea_project\\test\\netty\\io\\src\\main\\java\\com\\yj/io/nio/img/yj");
//        merge("D:\\software\\idea_project\\test\\netty\\io\\src\\main\\java\\com\\yj/io/nio/img/yj",
//                "D:\\software\\idea_project\\test\\netty\\io\\src\\main\\java\\com\\yj/io/nio/img/" + name + "下载文件" + "." + suffix, delete);
        System.out.println("下载文件名:" + name + data + "." + suffix);
        System.out.println("耗时时间:" + (System.currentTimeMillis() - start));
    }

    volatile static double sumLen = 0;
    public static void spilt4(URL url, String to) throws IOException {
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod("GET");
        //设置允许接收消息
        urlConnection.setDoInput(true);
        urlConnection.connect();

        int fileSize = urlConnection.getContentLength();//大小;
        System.out.println("文件总共大小：" + fileSize + "字节");

        InputStream inputStream = urlConnection.getInputStream();
        FileOutputStream fileOutputStream = null;

        //进度
        new Thread(() -> {
            while (sumLen < fileSize) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("显示进度:---------" + String.format("%.2f", (sumLen / fileSize) * 100) + "%-----------");
            }
        }).start();

        try {
            fileOutputStream = new FileOutputStream(to);
            byte[] bytes = new byte[1024 * 1024];
            int len;
            while ((len = inputStream.read(bytes)) != -1) {
                sumLen += len;
                fileOutputStream.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            inputStream.close();
            if (null != fileOutputStream) {
                fileOutputStream.flush();
                fileOutputStream.close();
            }
        }
        System.out.println("成功");
    }

    public static void spilt3(URL url, String to) throws IOException {
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod("GET");
        //设置允许接收消息
        urlConnection.setDoInput(true);
        urlConnection.connect();

        File file = new File(to);
        if (!file.exists()) {
            file.mkdirs();
        }

        int fileSize = urlConnection.getContentLength();//大小;
        System.out.println("文件总共大小：" + fileSize + "字节");
        int size = 20;//默认1m

        long mb100 = 1024 * 1024 * 100; //100mb
        long gb1 = 1024 * 1024 * 1024; //1gb
        long gb10 = 10L * 1024 * 1024 * 1024; //1gb

        if (mb100 <= fileSize && gb1 > fileSize) {
            size = 10;
        } else if (gb1 <= fileSize && gb10 > fileSize) {
            size = 100;
        } else if (gb10 <= fileSize){
            size = 1024;
        }

        // 将MB单位转为为字节B
        int m = size * 1024 * 1024;
        double m1 = m;

        // 计算最终会分成几个文件
        int count = (int) Math.ceil(fileSize / m1);
        System.out.println("文件分配：" + count + "块");

        ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2 - 1);
        try {
            CountDownLatch countDown = new CountDownLatch(count);
            for (int i = 0; i < count; i++) {
                int index = i;
                service.execute(() -> {
                    int beginPoint = index * m;
                    int endPoint;
                    if (index == (count - 1)) {
                        endPoint = fileSize;
                    } else {
                        endPoint = beginPoint + m;
                    }
                    InputStream inputStream = null;
                    FileOutputStream fileOutputStream = null;
                    try {
                        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                        conn.setRequestMethod("GET");
                        //设置允许接收消息
                        conn.setDoInput(true);
                        conn.connect();
                        inputStream = conn.getInputStream();
                        /**inputStream = urlConnection.getInputStream(); **/
                        inputStream.skip(beginPoint);

                        int start = beginPoint;
                        fileOutputStream = new FileOutputStream(to + "/" + index);
                        byte[] bytes = new byte[m];
                        int len;
                        System.out.println("第" + index + "分片开始下载:  起始字节数：" + beginPoint + ",结束字节数：" + endPoint);
                        while (start < endPoint) {
                            len = inputStream.read(bytes);
                            if ((start + len) >= endPoint) {
                                len = endPoint - start;
                                start = endPoint;
                            } else {
                                start += len;
                            }
                            if (len < 0) {
                                System.out.println("len=" + len + ",index=" + index);
                                start = endPoint;
                                break;
                            }
//                        System.out.println("线程名:"+Thread.currentThread().getName()+
//                                "--bytef="+bytes.length+",i="+ finalI +",len="+len);
                            fileOutputStream.write(bytes, 0, len);
                        }
                        System.out.println("------------------第" + index + "分片,下载完成-------------------------");
                        countDown.countDown();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            inputStream.close();
                            if (null != fileOutputStream) {
                                fileOutputStream.flush();
                                fileOutputStream.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            countDown.await();
            service.shutdown();
            System.out.println("分解成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static void spilt2(URL url, String to) throws IOException, InterruptedException {
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        InputStream inputStream = urlConnection.getInputStream();

        int fileSize = urlConnection.getContentLength();//大小;
        System.out.println("文件总共大小：" + fileSize + "字节");
        File file = new File(to);
        if (!file.exists()) {
            file.mkdirs();
        }
        ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        int size = 5 * 1024 * 1024; // m

        try {
            AtomicInteger ai = new AtomicInteger();
            boolean b = true;
            int i = 0;
            while (b) {
                byte[] bytes = new byte[size];
                int len = inputStream.read(bytes);
                if (len == -1) {
                    b = false;
                    break;
                }
                int finalI = i++;
                service.execute(() -> {
                    FileOutputStream fileOutputStream = null;
                    try {
                        fileOutputStream = new FileOutputStream(to + "/" + finalI);
//                        System.out.println("线程名:"+Thread.currentThread().getName()+
//                                "--bytef="+bytes.length+",i="+ finalI +",len="+len);
                        fileOutputStream.write(bytes, 0, len);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            fileOutputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            service.shutdown();
            while (!service.isTerminated()) { // 必须先shutdown(),这个方法才有效果  如果没有执行完就一直循环
            }
            System.out.println("分解成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            inputStream.close();
        }
    }

    public static void spilt(String from, String to) throws IOException, InterruptedException {
        File f = new File(from);
        FileInputStream in = new FileInputStream(f);
        FileOutputStream out = null;
        FileChannel inChannel = in.getChannel();
        FileChannel outChannel = null;
        int size = 1;//默认1m

        ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

        long mb100 = 1024 * 1024 * 100; //100mb
        long gb1 = 1024 * 1024 * 1024; //1gb

        if (mb100 <= f.length() && gb1 > f.length()) {
            size = 10;
        } else if (gb1 <= f.length()) {
            size = 100;
        }

        // 将MB单位转为为字节B
        long m = size * 1024 * 1024;
        // 计算最终会分成几个文件
        int count = (int) (f.length() / m);
        CountDownLatch countDown = new CountDownLatch(count);
        for (int i = 0; i <= count; i++) {
            // 生成文件的路径
            String t = to + "/" + i;
            File file = new File(to);
            if (!file.exists()) {
                file.mkdirs();
            }
            out = new FileOutputStream(t);
            outChannel = out.getChannel();
            int finalI = i;
            FileChannel finalOutChannel = outChannel;
            executorService.execute(() -> {
                // 从inChannel的m*i处，读取固定长度的数据，写入outChannel
                try {
                    if (finalI != count) {
                        inChannel.transferTo(m * finalI, m, finalOutChannel);
                    } else {// 最后一个文件，大小不固定，所以需要重新计算长度
                        inChannel.transferTo(m * finalI, f.length() - m * count, finalOutChannel);
                    }
                    countDown.countDown();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
        countDown.await();
        executorService.shutdown();
        System.out.println("分解成功");
        out.close();
        outChannel.close();
        in.close();
        inChannel.close();
    }

    public static void merge(String from, String to, Boolean delete) throws IOException {
        File t = new File(to);
        FileInputStream in = null;
        FileChannel inChannel = null;

        FileOutputStream out = new FileOutputStream(t, true);
        FileChannel outChannel = out.getChannel();

        File f = new File(from);
        // 获取目录下的每一个文件名，再将每个文件一次写入目标文件
        if (f.isDirectory()) {
            List<File> list = getAllFileAndSort(from);
            // 记录新文件最后一个数据的位置
            long start = 0;
            for (File file : list) {

                in = new FileInputStream(file);
                inChannel = in.getChannel();

                // 从inChannel中读取file.length()长度的数据，写入outChannel的start处
                outChannel.transferFrom(inChannel, start, file.length());
                start += file.length();
                in.close();
                inChannel.close();
            }
        }
        if (delete) {
            deleteFile(f);
        }
        System.out.println("合成成功");
        out.close();
        outChannel.close();
    }

    private static List<File> getAllFileAndSort(String dirPath) {
        File dirFile = new File(dirPath);
        File[] listFiles = dirFile.listFiles();
        List<File> list = Arrays.asList(listFiles);
        Collections.sort(list, (o1, o2) -> {
            return Integer.parseInt(o1.getName()) - Integer.parseInt(o2.getName());
        });
        return list;
    }


    //删除File对象中抽象的路径方法
    private static void deleteFile(File dir) {
        //将file封装的路径下对象转换为数组
        File[] files = dir.listFiles();
        //判断这个数组为不为空,如果不为空,就执行内部代码
        if (files != null) {
            for (File file : files) {
                //判断是否为文件
                if (file.isFile()) {
                    //如果为文件,执行删除
                    file.delete();
                } else {
                    //如果不为文件,就(递归)进入这个文件夹,删除文件
                    deleteFile(file);
                }
            }
            //删除全部文件后删除空文件夹,最后删除自己
            dir.delete();
        }
    }

    //获取后缀名
    final static Pattern pattern = Pattern.compile("\\S*[?]\\S*");

    private static String parseSuffix(String url) {
        Matcher matcher = pattern.matcher(url);
        String[] spUrl = url.toString().split("/");
        int len = spUrl.length;
        String endUrl = spUrl[len - 1];
        if (matcher.find()) {
            String[] spEndUrl = endUrl.split("\\?");
            return spEndUrl[0].split("\\.")[1];
        }
        return endUrl.split("\\.")[1];
    }

}


